﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DataContextListener.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the DataContextListener type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Windows
{
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Data;
    using Collections;

    public static class DataContextListener
    {
        #region Fields

        private static readonly DependencyProperty HandlersProperty = DependencyProperty.RegisterAttached("Handlers",
            typeof(WeakCollection<RoutedPropertyChangedEventHandler<object>>), typeof(DataContextListener), null);

        private static readonly DependencyProperty DataContextProperty = DependencyProperty.RegisterAttached("DataContext",
            typeof(object), typeof(DataContextListener), new PropertyMetadata(OnDataContextChanged));

        #endregion

        /// <summary>
        /// Called when the data context has changed.
        /// </summary>
        /// <param name="obj">The object whose data contact has changed.</param>
        /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        private static void OnDataContextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var handlers = GetHandlers(obj);
            if (handlers != null)
            {
                var count = NotifyHandlers(obj, handlers, new RoutedPropertyChangedEventArgs<object>(e.OldValue, e.NewValue));
                if (count == 0)
                {
                    Clear(obj);
                }
            }
        }

        /// <summary>
        /// Notifies the handlers.
        /// </summary>
        /// <param name="obj">The object whose data contact has changed.</param>
        /// <param name="handlers">The handlers to notify.</param>
        /// <param name="args">The <see cref="System.Windows.RoutedPropertyChangedEventArgs{Object}"/> instance containing the event data.</param>
        /// <returns>The number of handlers that were notified.</returns>
        private static int NotifyHandlers(DependencyObject obj, IEnumerable<RoutedPropertyChangedEventHandler<object>> handlers, RoutedPropertyChangedEventArgs<object> args)
        {
            var count = 0;
            foreach (var handler in handlers.Where(handler => handler != null))
            {
                handler(obj, args);
                count++;
            }
            return count;
        }

        /// <summary>
        /// Gets the handlers collection
        /// </summary>
        /// <param name="obj">The object whose handlers collection should be returned.</param>
        /// <returns>The handlers collection.</returns>
        private static ICollection<RoutedPropertyChangedEventHandler<object>> GetHandlers(DependencyObject obj)
        {
            return (WeakCollection<RoutedPropertyChangedEventHandler<object>>)obj.GetValue(HandlersProperty);
        }

        /// <summary>
        /// Gets the or create the handlers collection.
        /// </summary>
        /// <param name="obj">The object whose handlers collection should be returned.</param>
        /// <returns>The handler collection.</returns>
        private static ICollection<RoutedPropertyChangedEventHandler<object>> GetOrCreateHandlers(DependencyObject obj)
        {
            var handlers = GetHandlers(obj);
            if (handlers == null)
            {
                handlers = new WeakCollection<RoutedPropertyChangedEventHandler<object>>();
                obj.SetValue(HandlersProperty, handlers);
            }
            return handlers;
        }

        /// <summary>
        /// Adds the specified <paramref name="handler"/>
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="handler">The handler.</param>
        public static void AddHandler(DependencyObject obj, RoutedPropertyChangedEventHandler<object> handler)
        {
            #region Validate Arguments

            Guard.ArgumentNull("obj", obj);
            Guard.ArgumentNull("handler", handler);

            #endregion

            var handlers = GetOrCreateHandlers(obj);
            handlers.Add(handler);
            if (handlers.Count == 1)
            {
                BindingOperations.SetBinding(obj, DataContextProperty, new Binding());
            }
        }

        public static bool RemoveHandler(DependencyObject obj, RoutedPropertyChangedEventHandler<object> handler)
        {
            #region Validate Arguments

            Guard.ArgumentNull("obj", obj);
            Guard.ArgumentNull("handler", handler);

            #endregion

            var handlers = GetHandlers(obj);
            var result = handlers != null && handlers.Remove(handler);
            if (result)
            {
                if (handlers.Count == 0)
                {
                    Clear(obj);
                }
            }

            return result;
        }

        private static void Clear(DependencyObject obj)
        {
            obj.ClearValue(HandlersProperty);
            obj.ClearValue(DataContextProperty);
        }
    }
}
